import { Validator, ValidatorOptions } from './Validator';
import { I18nMessage } from '../i18n';

interface NumberOptions<IsRequired extends boolean> extends ValidatorOptions<number, IsRequired> {
  min?: number;
  minInclusive?: boolean;
  max?: number;
  maxInclusive?: boolean;
  onlyInteger?: boolean;
}

export class NumberValidator<T extends boolean = true> extends Validator<NumberOptions<T>> {
  public min(min: number, inclusive: boolean = true): this {
    this.config.min = min;
    this.config.minInclusive = inclusive;
    return this;
  }

  public max(max: number, inclusive: boolean = true): this {
    this.config.max = max;
    this.config.maxInclusive = inclusive;
    return this;
  }

  public onlyInteger(is: boolean = true): this {
    this.config.onlyInteger = is;
    return this;
  }

  public optional(): NumberValidator<false> {
    return super.optional();
  }

  public default(value: number): NumberValidator<true> {
    return super.default(value);
  }

  protected validateValue(i18n: I18nMessage, obj: Record<string, any>, key: string, superKeys: string[]): string | void {
    const { min, max, minInclusive, maxInclusive, onlyInteger } = this.config;
    let value = obj[key];

    if (typeof value !== 'number') {
      obj[key] = value = Number(value);
    }

    if (Number.isNaN(value)) {
      return i18n.number.invalid({
        attribute: this.getLabel(key, superKeys),
      });
    }

    if (onlyInteger && !Number.isInteger(value)) {
      return i18n.number.integer({
        attribute: this.getLabel(key, superKeys),
      });
    }

    if (min !== undefined && (minInclusive ? value < min : value <= min)) {
      if (max === undefined) {
       return i18n.number.tooSmall({
         attribute: this.getLabel(key, superKeys),
         min,
       })
      }

      return i18n.number.between({
        attribute: this.getLabel(key, superKeys),
        min,
        max,
      });
    }

    if (max !== undefined && (maxInclusive ? value > max : value >= max)) {
      if (min === undefined) {
        return i18n.number.tooLarge({
          attribute: this.getLabel(key, superKeys),
          max,
        });
      }

      return i18n.number.between({
        attribute: this.getLabel(key, superKeys),
        min,
        max,
      });
    }

    return;
  }
}
